/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Filename: PostProc.cs
 * Author: Michael Quandt
 * Version: 2.0
 * 
 * Copyright (c) 2007 Michael Quandt
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace PostProcSample
{
    /// <summary>
    /// The base postprocessor class. Inherit from this and customise for the technique required.
    /// To use the class, you need to render your scene to a RenderTarget2D yourself, and then pass in
    /// the texture which you can get from the method RenderTarget2D.GetTexture(), and also provide a ref RenderTarget2D
    /// to be the buffer rendered to. If you want to render to the backbuffer, just do not pass a RenderTarget2D.
    /// </summary>
    public class PostProc
    {
        #region Private Variables
        private Effect fx;

        private VertexDeclaration vDecl;
        private VertexPositionTexture[] verts;

        private EffectParameterCollection parameters;
        private EffectTechniqueCollection techniques;
        private readonly string effectName;

        private readonly GraphicsDevice gfx;
        private readonly ContentManager content;
        #endregion

        /// <summary>
        /// Inherit this to set your parameters for the shader, called each render.
        /// Use the EffectParameterCollection named _params to set the parameters.
        /// </summary>
        protected virtual void SetParams()
        {
        }

        /// <summary>
        /// Inherit this and apply your required renderstates here.
        /// </summary>
        protected void SetRenderStates()
        {
            gfx.RenderState.DepthBufferEnable = false;
            gfx.RenderState.DepthBufferWriteEnable = false;
        }

        /// <summary>
        /// This method lets youo change the current techique of the Effect by using a string name to identify the technique in the shader, and calling it here.
        /// </summary>
        /// <param name="techniqueName">The name of the technique in the shader to be used.</param>
        public void SetTechnique(string techniqueName)
        {
            if(techniques != null)
            {
                if (techniques[techniqueName] != null)
                    fx.CurrentTechnique = techniques[techniqueName];
            }
        }

        /// <summary>
        /// The constructor for the PostProcessor class, handles acquiring the GraphicsDevice and the ContentManager.
        /// Should be called in LoadContent.
        /// </summary>
        /// <param name="AssetName"></param>
        /// <param name="game"></param>
        public PostProc(string AssetName, Game game)
        {
            effectName = AssetName;
            gfx = game.GraphicsDevice;
            content = game.Content;
            if(content != null)
                LoadGraphicsData();
        }

        /// <summary>
        /// Loads the objects needed by the graphics card for rendering the fullscreen quad.
        /// </summary>
        public void LoadGraphicsData()
        {
            fx = content.Load<Effect>(effectName);

            verts = new VertexPositionTexture[]
            {
                new VertexPositionTexture(
                    new Vector3(-1f, -1f, 0.5f),
                    new Vector2(0,1)),
                new VertexPositionTexture(
                    new Vector3(-1f, 1f, 0.5f),
                    new Vector2(0,0)),
                new VertexPositionTexture(
                    new Vector3(1f, -1f, 0.5f),
                    new Vector2(1,1)),
                new VertexPositionTexture(
                    new Vector3(1f,1f,0.5f),
                    new Vector2(1,0)),
            };

            vDecl = new VertexDeclaration(gfx, VertexPositionTexture.VertexElements);

            parameters = fx.Parameters;
            techniques = fx.Techniques;
        }

        /// <summary>
        /// Sets the source textures for the shader using the prefix to get the correct name.
        /// The prefix will be appended by the zero-based index of the texture to create the parameter name.
        /// This way you can call the textures in your SetParams method by easily using the prefix followed by the number of the texture. (ZERO BASED)
        /// </summary>
        /// <param name="prefix">String holding the prefix of the parameter name.</param>
        /// <param name="sources">Variable number of textures that will be assigned to the shader.</param>
        public void SetSourceTextures(string prefix, params Texture2D[] sources)
        {
            // Protect against idiots
            if (sources.Length > 0)
            {
                for (int i = 0; i < sources.Length; i++)
                {
                    if (parameters[prefix + i.ToString()] != null)
                        parameters[prefix + i.ToString()].SetValue(sources[i]);
                }
            }
        }

        /// <summary>
        /// Renders the post-effect to the render targets specified, if none are specified, it renders to whatever is currently set.
        /// Multiple destination targets can be provided for MRT, but please check yourself if the computer can support MRT beforehand.
        /// Once rendering is done, set the RenderTarget to something else to resolve the texture (XNA2.0 does not have ResolveRenderTarget anymore,
        /// or if you were rendering to the backbuffer, then enjoy the image. :)
        /// </summary>
        /// <param name="dest">Variable number of RenderTargets that will be rendered to.</param>
        public void Render(params RenderTarget2D[] dest)
        {
            if (dest.Length > 0)
            {
                for (int i = 0; i < dest.Length; i++)
                {
                    gfx.SetRenderTarget(i, dest[i]);
                }
            }

            // Begin effect
            fx.Begin(SaveStateMode.SaveState);

            // Set the renderstates (see function)
            SetRenderStates();

            // Set the parameters (see function)
            SetParams();

            // Loop through the passes
            for (int i = 0; i < fx.CurrentTechnique.Passes.Count; i++)
            {
                EffectPass p = fx.CurrentTechnique.Passes[i];
                // Begin the pass
                p.Begin();

                // Render the fullscreen quad using the effect
                gfx.VertexDeclaration = vDecl;
                gfx.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleStrip, verts, 0, 2);

                p.End();
            }
            // Finished with effect
            fx.End();
        }
    }
}